use rstest::rstest;

use super::super::*;
use crate::configuration;
use crate::tests::fixtures::{
    rand_usize,
    random_tile_selection_policy,
    random_vacancy_policy,
    test_data,
};

#[rstest]
#[case::three_wildcard_no_neighbor_associations_no_excluded_connectors_six_tiles(
    Connectors {
        north: 0,
        east: 2,
        south: 0,
        west: 0,
    },
    vec![],
    HashSet::new(),
    Some(TileConnection {
        tiles: vec![0, 1, 4, 6, 7, 8],
        weights: vec![4.0, 4.0, 5.0, 1.0, 2.0, 8.0],
    }),
    None,
)]
#[case::three_wildcard_no_neighbor_associations_no_excluded_connectors_no_tiles(
    Connectors {
        north: 3,
        east: 0,
        south: 0,
        west: 0,
    },
    vec![],
    HashSet::new(),
    None,
    Some(Error::TileNotFoundException(
        "no tiles available with Connectors { north: “i”, east: 🃟, south: 🃟, west: 🃟 }"
            .to_string()
    )),
)]
#[case::no_wildcard_no_neighbor_associations_no_excluded_connectors_no_tiles(
    Connectors {
        north: 1,
        east: 2,
        south: 3,
        west: 4,
    },
    vec![],
    HashSet::new(),
    None,
    Some(Error::TileNotFoundException(
        "no tiles available with Connectors { north: None, east: “e”, south: “i”, west: “T” }"
            .to_string()
    )),
)]
#[case::two_wildcards_no_neighbor_associations_no_excluded_connectors_no_tiles(
    Connectors {
        north: 2,
        east: 0,
        south: 0,
        west: 1,
    },
    vec![],
    HashSet::new(),
    None,
    Some(Error::TileNotFoundException(
        "no tiles available with Connectors { north: “8”, east: 🃟, south: 🃟, west: None }"
            .to_string()
    )),
)]
#[case::three_wildcards_no_neighbor_associations_no_excluded_connectors_no_tiles(
    Connectors {
        north: 0,
        east: 0,
        south: 0,
        west: 7,
    },
    vec![],
    HashSet::new(),
    Some(TileConnection {
        tiles: vec![3, 4, 8],
        weights: vec![1.0, 5.0, 8.0],
    }),
    None,
)]
#[case::no_wildcard_no_neighbor_associations_two_excluded_connectors_no_tiles(
    Connectors {
        north: 4,
        east: 3,
        south: 3,
        west: 2,
    },
    vec![],
    {
        let mut excluded_connectors = HashSet::new();
        excluded_connectors.insert(Connectors {north: 5, east: 3, south: 3, west: 2});
        excluded_connectors.insert(Connectors {north: 2, east: 4, south: 4, west: 3});
        excluded_connectors
    },
    None,
    Some(Error::TileNotFoundException(
        "no tiles available with Connectors { north: “l”, east: “g”, south: “i”, west: “e” }"
            .to_string()
    )),
)]
#[case::three_wildcards_no_neighbor_associations_two_excluded_connectors_one_tile(
    Connectors {
        north: 2,
        east: 0,
        south: 0,
        west: 0,
    },
    vec![],
    {
        let mut excluded_connectors = HashSet::new();
        excluded_connectors.insert(Connectors {north: 2, east: 2, south: 9, west: 2});
        excluded_connectors.insert(Connectors {north: 2, east: 2, south: 6, west: 7});
        excluded_connectors
    },
    Some(TileConnection {
        tiles: vec![0],
        weights: vec![4.0],
    }),
    None,
)]
#[case::two_wildcards_one_neighbor_association_no_excluded_connectors_two_tiles(
    Connectors {
        north: 0,
        east: 2,
        south: 0,
        west: 7,
    },
    vec![
        {
            let mut association = HashSet::new();
            association.insert(Direction::North);
            association.insert(Direction::South);
            association.insert(Direction::East);
            association.insert(Direction::West);
            association
        },
    ],
    HashSet::new(),
    Some(TileConnection {
        tiles: vec![4, 8],
        weights: vec![5.0, 8.0],
    }),
    None,
)]
#[case::two_wildcards_two_neighbor_associations_no_excluded_connectors_one_tile(
    Connectors {
        north: 0,
        east: 2,
        south: 0,
        west: 7,
    },
    vec![
        {
            let mut association = HashSet::new();
            association.insert(Direction::North);
            association.insert(Direction::East);
            association.insert(Direction::West);
            association
        },
        {
            let mut association = HashSet::new();
            association.insert(Direction::South);
            association
        },
    ],
    HashSet::new(),
    Some(TileConnection {
        tiles: vec![4],
        weights: vec![5.0],
    }),
    None,
)]
#[case::two_wildcards_one_neighbor_association_no_excluded_connectors_two_tiles(
    Connectors {
        north: 11,
        east: 0,
        south: 11,
        west: 0,
    },
    vec![
        {
            let mut association = HashSet::new();
            association.insert(Direction::West);
            association.insert(Direction::East);
            association
        },
    ],
    HashSet::new(),
    Some(TileConnection {
        tiles: vec![11, 12],
        weights: vec![7.0, 3.0],
    }),
    None,
)]
#[case::three_wildcards_two_neighbor_associations_three_excluded_connectors_two_tiles(
    Connectors {
        north: 0,
        east: 1,
        south: 0,
        west: 0,
    },
    vec![
        {
            let mut association = HashSet::new();
            association.insert(Direction::North);
            association
        },
        {
            let mut association = HashSet::new();
            association.insert(Direction::South);
            association
        },
    ],
    {
        let mut excluded_connectors = HashSet::new();
        excluded_connectors.insert(Connectors {north: 12, east: 1, south: 13, west: 12});
        excluded_connectors.insert(Connectors {north: 11, east: 10, south: 11, west: 1});
        excluded_connectors.insert(Connectors {north: 14, east: 1, south: 15, west: 13});
        excluded_connectors
    },
    Some(TileConnection {
        tiles: vec![10, 12],
        weights: vec![13.0, 3.0],
    }),
    None,
)]
#[case::three_wildcards_one_neighbor_association_three_excluded_connectors_two_tiles(
    Connectors {
        north: 0,
        east: 1,
        south: 0,
        west: 0,
    },
    vec![
        {
            let mut association = HashSet::new();
            association.insert(Direction::North);
            association.insert(Direction::South);
            association
        },
    ],
    {
        let mut connectors = HashSet::new();
        connectors.insert(Connectors {north: 12, east: 1, south: 13, west: 12});
        connectors.insert(Connectors {north: 11, east: 10, south: 11, west: 1});
        connectors.insert(Connectors {north: 14, east: 1, south: 15, west: 13});
        connectors
    },
    Some(TileConnection {
        tiles: vec![10, 12, 15],
        weights: vec![13.0, 3.0, 1.1],
    }),
    None,
)]
#[case::three_wildcards_one_neighbor_association_six_excluded_connectors_no_tiles(
    Connectors {
        north: 0,
        east: 1,
        south: 0,
        west: 0,
    },
    vec![
        {
            let mut association = HashSet::new();
            association.insert(Direction::North);
            association.insert(Direction::South);
            association
        },
    ],
    {
        let mut connectors = HashSet::new();
        connectors.insert(Connectors {north: 12, east: 1, south: 13, west: 12});
        connectors.insert(Connectors {north: 11, east: 10, south: 11, west: 1});
        connectors.insert(Connectors {north: 14, east: 1, south: 15, west: 13});
        connectors.insert(Connectors {north: 11, east: 1, south: 11, west: 1});
        connectors.insert(Connectors {north: 11, east: 1, south: 11, west: 11});
        connectors.insert(Connectors {north: 1, east: 1, south: 15, west: 1});
        connectors
    },
    None,
    Some(Error::TileNotFoundException(
        "no tiles available with Connectors { north: 🃟, east: None, south: 🃟, west: 🃟 }"
            .to_string()
    )),
)]
fn test_slot_grid_determine_tile_connection(
    #[case] connectors: Connectors,
    #[case] neighbor_associations: Vec<HashSet<Direction>>,
    #[case] excluded_connectors: HashSet<Connectors>,
    #[values(MissingTilePolicy::Avoid, MissingTilePolicy::Exit)]
    missing_tile_policy: MissingTilePolicy,
    #[case] expected_tile_connection: Option<TileConnection>,
    #[case] expected_error: Option<Error>,
) {
    let slot_grid = SlotGrid::new(
        rand_usize(1, 10),
        rand_usize(2, 5),
        TileList::new(
            configuration::load(Some(test_data("tiles_connection/config.yaml")), None)
                .expect("the configuration should be valid"),
        )
        .expect("tiles_connection/config.yaml should be accessible"),
        None,
        missing_tile_policy,
        random_tile_selection_policy(),
        random_vacancy_policy(),
    )
    .expect("the number of rows, columns as well as the border tile ID should be valid");
    let tile_connection = slot_grid.determine_tile_connection(
        &connectors,
        &neighbor_associations,
        &excluded_connectors,
    );

    if let Some(expected_error) = expected_error {
        let error = tile_connection.expect_err("Expected an Err, got Ok");
        assert_eq!(error, expected_error)
    } else {
        assert_eq!(
            tile_connection.expect("expected Ok, got Err"),
            expected_tile_connection.expect("expected Some, got None"),
        );
    }
}
